package models

import (
	"code.google.com/p/jesus-1496/auth/password"
	"fmt"
)

var userStore UserStorer

type UserError struct {
	errorText string
}

func NewUserError(errorMessage string) error {
	return UserError{errorMessage}
}

func (u UserError) Error() string {
	return "UserError: " + u.errorText
}

func NewAuthenticationError(u *User, errorString string) error {
	return NewUserError(fmt.Sprintf("Cannot authenticate user %s: %s.", u.Email, errorString))
}

type User struct {
	Id             int64
	Email          string
	HashedPassword string
	IsAdmin        bool
	IsActive       bool
}

func NewUser(email, pw string, isAdmin bool) (*User, error) {
	if err := password.ValidatePassword(pw); err != nil {
		return nil, err
	}

	hashInput, err := password.GenerateHashInputFromStrings(email, pw)
	if err != nil {
		return nil, err
	}

	hash, err := password.GenerateHash(hashInput)
	if err != nil {
		return nil, err
	}

	u := User{Email: email, HashedPassword: hash, IsAdmin: isAdmin}

	return &u, nil
}

func (u *User) Authenticate(pw string) error {
	if err := password.ValidatePassword(pw); err != nil {
		//Password cannot be correct, do not need to hash
		return NewAuthenticationError(u, err.Error())
	}

	hashInput, err := password.GenerateHashInputFromStrings(u.Email, pw)
	if err != nil {
		return NewAuthenticationError(u, err.Error())
	}

	err = password.CompareHashAndInput(u.HashedPassword, hashInput)
	if err != nil {
		return NewAuthenticationError(u, err.Error())
	}

	return nil
}

func (u *User) Save() error {
	panicIfUnsetUserStorer()
	if err := userStore.Save(u); err != nil {
		return err
	}
	return nil
}

func LoadUser(email string) (*User, error) {
	panicIfUnsetUserStorer()
	return userStore.Load(email)
}

func UserExists(email string) bool {
	panicIfUnsetUserStorer()
	return userStore.Exists(email)
}

func (u *User) Delete() error {
	panicIfUnsetUserStorer()
	return userStore.Delete(u.Email)
}

//Should be used at the beginning of methods which make user of the UserStorer. There is no point trying to
//recover in this situation.
func panicIfUnsetUserStorer() {
	if userStore == nil {
		panic("Cannot Save user with no UserStorer set. UserStorer can be set by calling SetUserStorer.")
	}
}

func (u *User) ChangePassword(oldPassword, newPasswordA, newPasswordB string) error {
	//Need to 'Authenticate' with old password before changing password.
	if err := u.Authenticate(oldPassword); err != nil {
		return err
	}

	if newPasswordA != newPasswordB {
		return NewUserError("New passwords do not match.")
	}

	hashInput, err := password.GenerateHashInputFromStrings(u.Email, newPasswordA)
	if err != nil {
		return err
	}

	hash, err := password.GenerateHash(hashInput)
	if err != nil {
		return err
	}

	u.HashedPassword = hash
	err = u.Save()
	if err != nil {
		return err
	}

	return nil
}

//UserStorer contains the basic methods for the User that require use of external storage.
// This needs to be set before the user can be used.
//TODO: Document what each call does in certain situations (eg Delete a user that doesn't exist)
type UserStorer interface {
	Save(*User) error
	Load(string) (*User, error)
	Exists(string) bool
	Delete(string) error
}

func SetUserStore(us UserStorer) {
	userStore = us
}
